function Decoder() {
    this.logger             = new Logger("Decoder");
    this.accurateSeek       = true;
    this.wasmLoaded         = false;
    this.tmpReqQue          = [];
    this.cacheBuffer        = null;
    this.cacheBufferSize    = 65535;
    this.decodeTimer        = null;
    this.videoCallbackPtr   = null;
    this.handle             = null;
    this.firstI             = 0;
    this.dataQueue          = [];
}

Decoder.prototype.openDecoder = function () {
    this.handle = Module._open_decoder(this.videoCallbackPtr);
    if (this.handle ) {
        this.logger.logInfo("_open_decoder.");
        this.cacheBuffer = Module._malloc(this.cacheBufferSize);
        var objData = {
            t: kOpenDecoderRsp,
            e: 0
        };
        self.postMessage(objData);  
        
        this.startDecoding();
    }
};

Decoder.prototype.closeDecoder = function () {
    if (this.decodeTimer) {
        clearInterval(this.decodeTimer);
        this.decodeTimer = null;
        this.logger.logInfo("Decode timer stopped.");
    }

    Module._close_decoder();
    this.logger.logInfo("_close_decoder.");
    if (this.cacheBuffer != null) {
        Module._free(this.cacheBuffer);
        this.cacheBuffer = null;
    }
};

Decoder.prototype.videoCallback = function (w, h, buff, size) {
    var outArray = Module.HEAPU8.subarray(buff, buff + size);
    var data = new Uint8Array(outArray);
    var objData = {
        t: kVideoFrame,
        s: 0,
        w: w,
        h: h,
        d: data
    };
    self.postMessage(objData, [objData.d.buffer]);
}


Decoder.prototype.startDecoding = function (interval) {
    this.logger.logInfo("Decode timer start.");
    if (this.decodeTimer) {
        clearInterval(this.decodeTimer);
    }
    this.decodeTimer = setInterval(this.decode.bind(this), 30);

    setInterval(function () {
        this.logger.logDebug("queue:" + this.dataQueue.length);
    }.bind(this), 1000);    
};

Decoder.prototype.decode = function () {
    if(this.dataQueue.length > 0) {
        let data = this.dataQueue.shift();
        while (data.length > this.cacheBufferSize) {
            // this.logger.logError("data.length > this.cacheBufferSize");
            if(this.cacheBuffer) {
                Module._free(this.cacheBuffer);
                this.cacheBuffer = Module._malloc(this.cacheBufferSize * 2);
                this.cacheBufferSize  = this.cacheBufferSize * 2;
                this.logger.logInfo('data length:' + data.length + ' new cacheBufferSize:' + this.cacheBufferSize);
            }
        }

        Module.HEAPU8.set(data, this.cacheBuffer);
        // this.logger.logDebug('decode start');
        Module._decode(this.handle, this.cacheBuffer, data.length);
        // this.logger.logDebug('decode over');
    }
};

Decoder.prototype.sendData = function (data, coding) {
    if (this.firstI == 0 && coding != 0) { //wait for fist I frame
        return ;
    } else {
        this.firstI = 1;
    }
    // this.logger.logDebug("sendData start dataQueue.length:" + this.dataQueue.length + "  add data:" + data.length);
    this.dataQueue.push(data);
    // this.logger.logDebug("sendData end dataQueue.length:" + this.dataQueue.length);
    
};

Decoder.prototype.processReq = function (req) {
    //this.logger.logInfo("processReq " + req.t + ".");
    switch (req.t) {
        case kOpenDecoderReq:
            this.openDecoder();
            break;
        case kCloseDecoderReq:
            this.closeDecoder();
            break;
        case kStartDecodingReq:
            this.startDecoding(req.i);
            break;
        case kPauseDecodingReq:
            this.pauseDecoding();
            break;
        case kFeedDataReq:
            this.sendData(req.d, req.coding);
            break;
        case kSeekToReq:
            this.seekTo(req.ms);
            break;
        default:
            this.logger.logError("Unsupport messsage " + req.t);
    }
};

Decoder.prototype.cacheReq = function (req) {
    if (req) {
        this.tmpReqQue.push(req);
    }
};

Decoder.prototype.onWasmLoaded = function () {
    this.logger.logInfo("Wasm loaded.");
    this.wasmLoaded = true;
    this.videoCallbackPtr = Module.addFunction(this.videoCallback.bind(this), 'viiii');

    while (this.tmpReqQue.length > 0) {
        var req = this.tmpReqQue.shift();
        this.processReq(req);
    }
};


self.Module = {
    onRuntimeInitialized: function () {
        onWasmLoaded();
    }
};
self.importScripts("common.js");
self.importScripts("libffmpeg.js");
self.decoder = new Decoder;
self.onmessage = function (evt) {
    if (!self.decoder) {
        console.log("[ER] Decoder not initialized!");
        return;
    }
    var req = evt.data;
    if (!self.decoder.wasmLoaded) {
        self.decoder.cacheReq(req);
        self.decoder.logger.logInfo("Temp cache req " + req.t + ".");
        return;
    }
    self.decoder.processReq(req);
};

function onWasmLoaded() {
    if (self.decoder) {
        self.decoder.onWasmLoaded();
    } else {
        console.log("[ER] No decoder!");
    }
}
